/******************************************************************************/
package jamie.org.sil.www.usxUtils;

import java.io.*;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;


/******************************************************************************/
/**
   Miscellaneous utilities.
*/

public class Utils
{
  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Public                                   **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /**
   * Copies a text file to a given stream.
   * 
   * @param stream
   * 
   * @param filePath
   * 
   * @param encoding (eg "UTF8")
   * 
   * @throws IOException
   */
  
  public static void copyFileToStream (MyOutputStream stream,
                                       String filePath,
                                       String encoding) throws IOException
  {
    /*--------------------------------------------------*/
    FileInputStream fis = new FileInputStream(filePath);
    UnicodeReader unicodeReader = new UnicodeReader(fis, encoding);
    BufferedReader rdr = new BufferedReader(unicodeReader);
    



    /*--------------------------------------------------*/
    String s = null;
    while ((s = rdr.readLine()) != null) { stream.write(s); stream.newLine(); }

    
    
    /*--------------------------------------------------*/
    rdr.close();
    unicodeReader.close();
    fis.close();
  }

    
  /****************************************************************************/
  /** Clears out a folder in its entirety (but leaves the folder itself in
   *  place).
   *
   * @param folderName
   */

  public static void deleteEverythingFromFolder (String folderName)
  {
    deleteFoldersFromFolder(folderName);
    deleteFilesFromFolder(folderName, "");
  }


  /****************************************************************************/
  /**
   * Deletes a file without complaining if it doesn't exist.
   *
   * @param fileName
   */

  public static void deleteFile (String fileName) { deleteFile(new File(fileName)); }


  /****************************************************************************/
  /**
   * Deletes a file without complaining if it doesn't exist.
   *
   * @param f
   */

  public static void deleteFile (File f)
  {
    try { f.delete(); } catch(Exception e) {}
  }


  /****************************************************************************/
  /**
   * Deletes from a given folder all files with a given suffix.  I'd normally
   * expect the suffix to be the file extension, but the routine actually
   * looks merely for anything which ends with the given suffix, so you
   * have slightly more flexibility than that.
   *
   * @param folderName
   * @param suffix
   */

  public static void deleteFilesFromFolder (String folderName, String suffix)
  {
    try
    {
      /*--------------------------------------------------------*/
      File f = new File(folderName);
      File[] children;
      FilenameFilter filter;
      m_Suffix = suffix;



      /*--------------------------------------------------------*/
      filter = new FilenameFilter()
      {
        @Override public boolean accept(File dir, String name) { return name.endsWith(m_Suffix); };
      };

      children = f.listFiles(filter);
      if (null != children)
        for (int i = 0; i < children.length; ++i)
          deleteFile(children[i]);
    }
    catch(Exception e)
    {
    }
  }


  /****************************************************************************/
  /**
   * Deletes from a given folder all files with a given suffix.  I'd normally
   * expect the suffix to be the file extension, but the routine actually
   * looks merely for anything which ends with the given suffix, so you
   * have slightly more flexibility than that.
   *
   * @param folderName
   * @param suffix
   */

  public static void deleteFoldersFromFolder (String folderName)
  {
    deleteFoldersFromFolder(new File(folderName), false);
  }


  /****************************************************************************/
  /** Copy one file to another.  Really can't believe this isn't available
   *  built-in from the File class, but apparently not.
   *
   * @param outFilePath
   * @param inFilePath
   * @throws IOException
   *
   */

  public static void fileCopy (String outFilePath, String inFilePath) throws IOException
  {
    File sourceFile = new File(inFilePath);
    File targetFile = new File(outFilePath);

    if (!targetFile.exists()) targetFile.createNewFile();

    FileChannel source = null;
    FileChannel target = null;
    try
    {
      source = new FileInputStream(sourceFile).getChannel();
      target = new FileOutputStream(targetFile).getChannel();
      target.transferFrom(source, 0, source.size());
    }
    finally
    {
     if (null != source) source.close();
     if (null != target) target.close();
    }
  }


  /****************************************************************************/
  /** Copy one folder to another.  Really can't believe this isn't available
   *  built-in from the File class, but apparently not.
   *
   * @param outFolderPath
   * @param inFolderPath
   * @throws IOException
   *
   */

  public static void folderCopy (String outFolderPath, String inFolderPath) throws IOException
  {
    File dstFolder = new File(outFolderPath);
    File srcFolder = new File(inFolderPath);

    if (srcFolder.isDirectory())
    {
      if (!dstFolder.exists()) dstFolder.mkdir();

      String[] kids = srcFolder.list();
      for (int i = 0; i < kids.length; i++)
        folderCopy(new File(dstFolder, kids[i]).getAbsolutePath(),
                   new File(srcFolder, kids[i]).getAbsolutePath());
    }
    else // Source is a single file.
    {
      if (dstFolder.isDirectory())
        fileCopy(new File(dstFolder, srcFolder.getName()).getAbsolutePath(), inFolderPath);
      else
        fileCopy(outFolderPath, inFolderPath);
    }
  }


  /****************************************************************************/
  /** Gets the names of all files in a given folder whose names match a given
   *  pattern.  The return value is a string listing the filenames (not path
   *  names), separated by sep.
   *
   * @param folder
   * @param pat Pattern-match name.
   * @param regexPattern If true, this is a regex pattern.  If false, it's a
   *                     file system pattern, and needs conversion to work
   *                     with regex.
   * @param sep Separator.
   * 
   * @return Filenames if any match, otherwise an empty string.
   */

  public static String getAllMatchingFilesFromFolder (String folderName,
                                                      String pat,
                                                      boolean regexPattern,
                                                      String sep) throws IOException
  {
    /*--------------------------------------------------------*/
    String fileName = pat;
    if (!regexPattern)
    {
      fileName = fileName.replaceAll("\\.", "\\.");
      fileName = fileName.replaceAll("\\*", ".*");
    }

    folderName = (new File(folderName)).getCanonicalPath();

    String fileList = getFileNamesInFolder(folderName, "|", false);
    String[] files = fileList.split("\\|");
    String res = "";

    for (int i = 0; i < files.length; ++i)
      if (files[i].matches(fileName)) res += sep + files[i];

    if (res.length() > 0) res = res.substring(sep.length());
    return res;
   }


  /****************************************************************************/
  /** Gets the names of all files in a given folder.
   *
   * @param folder
   * @param sep Separator to be used between file names.
   * @param includeFolderName True if names should include the name of the folder.
   * @return List of file names.
   */

  public static String getFileNamesInFolder (String folder,
                                             String sep,
                                             boolean includeFolderName)
  {
    /*--------------------------------------------------------*/
    if (0 == folder.length()) return "";
    File f = new File(folder);
    File[] children;
    String res = "";



    /*--------------------------------------------------------*/
    children = f.listFiles();
    if (null != children)
      for (int i = 0; i < children.length; ++i)
        res += sep + (includeFolderName ? children[i].getPath() : children[i].getName());


    /*--------------------------------------------------------*/
    return 0 == res.length() ? "" : res.substring(sep.length());
   }


  /****************************************************************************/
  /** Gets the names of all files in a given folder.
   *
   * @param folder
   * @param pat Pattern-match name.
   * @param regexPattern If true, this is a regex pattern.  If false, it's a
   *                     file system pattern, and needs conversion to work
   *                     with regex.
   * @return Filename if precisely one matches, otherwise an empty string.
   */

  public static String getSingleMatchingFileFromFolder (String folderName,
                                                        String pat,
                                                        boolean regexPattern) throws IOException
  {
    /*--------------------------------------------------------*/
    String fileName = pat;
    if (!regexPattern)
    {
      fileName = fileName.replaceAll("\\.", "\\.");
      fileName = fileName.replaceAll("\\*", ".*");
    }

    folderName = (new File(folderName)).getCanonicalPath();

    String fileList = getFileNamesInFolder(folderName, "|", false);
    String[] files = fileList.split("\\|");
    String res = "";

    for (int i = 0; i < files.length; ++i)
    {
      if (files[i].matches(fileName))
      {
        if (0 == res.length())
        {
          if (!folderName.endsWith(File.separator)) folderName += File.separatorChar;
          res = folderName + files[i];
        }
        else
          return "";
      }
    }

    return res;
   }


  /****************************************************************************/
  /** Extracts the UBS book abbreviation from a file name.  This relies on the
   *  fact that we have a convention that filenames normally start with an
   *  n-digit book number, followed by the book abbreviation (it's the
   *  abbreviation we return here).  If we don't find n digits at the start
   *  of the filename, we simply return everything bar the extension.
   *  <p>
   *
   * Whether this really belongs in a general purpose utility class is a bit of
   * a moot point.
   *
   * @param fileName
   * @return UBS book abbreviation.
   */

  public static String getUbsBookAbbreviationFromFileName (String fileName)
  {
    fileName = (new File(fileName)).getName();
    if (fileName.matches("\\d\\d.*"))
      return fileName.substring(2, 5);
    else
      return fileName.substring(0, fileName.lastIndexOf(".") - 3);
  }


  /****************************************************************************/
  /** Gets the output file path corresponding to a given input file name or path.
   *
   * @param inputFilePathOrName
   * @return Corresponding output file path.
   * @throws ConverterException
   */

  public static String outputDataFilePath (String inputFilePathOrName) throws ConverterException
  {
    return Config.instance().getOutputFolderPath() + outputFileName(inputFilePathOrName);
  }


  /****************************************************************************/
  /** Gets the output file name corresponding to a given input file name or path.
   *
   * @param inputFilePathOrName
   * @return Corresponding output file path.
   * @throws ConverterException
   */

  public static String outputFileName (String inputFilePathOrName) throws ConverterException
  {
    String s = (new File(inputFilePathOrName)).getName();
    int p = s.toLowerCase().indexOf("." + Config.instance().getInputFileExtension().toLowerCase());
    s = s.substring(0, p) + "." + Config.instance().getOutputFileExtension();
    return s;
  }


  /****************************************************************************/
  /**
   * Copies all selected files to the appropriate location.
   *
   * @param outputFolderPath
   *
   * @param filePaths File paths or file names.
   *
   * @param inputFolderPath Optional: prefixed to each input file name.
   *
   * @throws IOException
   */

  public void selectiveFileCopy (String outputFolderPath, 
				 ArrayList<String> filePaths,
				 String inputFolderPath) throws IOException
  {
    /**************************************************************************/
    if (0 == filePaths.size()) return;



    /**************************************************************************/
    /* If we need a subfolder to hold the data, make sure it exists. */

    File folder = new File(outputFolderPath);
    if (!folder.exists()) folder.mkdirs();



    /**************************************************************************/
    /* Copy the files. */

    for (String filePath : filePaths)
    {
      String fileName = (new File(filePath)).getName();
      Utils.fileCopy(outputFolderPath + fileName, inputFolderPath + filePath);
    }
  }


  /****************************************************************************/
  /**
     Sorts strings into ascending order of length.  The order is undefined if
     several strings are of the same length.

     @param s Array of strings to be sorted.

     @return Sorted array.
  */

  public static String[] sortByAscendingLength (String[] s)
  {
    for (int i = 0; i < s.length; ++i) s[i] = String.format("%03d", s[i].codePointCount(0, s[i].length())) + "¬Jamie¬" + s[i];
    Arrays.sort(s);
    for (int i = 0; i < s.length; ++i) s[i] = s[i].split("¬Jamie¬")[1];
    return s;
  }


  /****************************************************************************/
  /** Replaces apostrophe and double quote marks in a string with HTML markers.
   *
   * @param s String to be processed.
   * @return Processed string.
   */

  public static String unquotify (String s)
  {
      s = s.replaceAll("\\\"", "&quot;");
      return s.replaceAll("\\\'", "&#39;");
  }


  /****************************************************************************/
  /**
   * Converts a string to an HTML-safe version.  Taken from the web --
   * http://stackoverflow.com/questions/1224996/java-convert-string-to-html-string.
   * 
   * @param string
   * @return HTML version.
   */
  
  public static String stringToHtmlString(String string)
  {
    StringBuilder sb = new StringBuilder(string.length());
    boolean lastWasBlankChar = false;
    int len = string.length();
    char c;
    
    if (true) return string;

    for (int i = 0; i < len; i++)
        {
        c = string.charAt(i);
        if (c == ' ') {
            // blank gets extra work,
            // this solves the problem you get if you replace all
            // blanks with &nbsp;, if you do that you lose 
            // word breaking
            if (lastWasBlankChar) {
                lastWasBlankChar = false;
                sb.append("&nbsp;");
                }
            else {
                lastWasBlankChar = true;
                sb.append(' ');
                }
            }
        else {
            lastWasBlankChar = false;
            //
            // HTML Special Chars
            if (c == '"')
                sb.append("&quot;");
            else if (c == '&')
                sb.append("&amp;");
            else if (c == '<')
                sb.append("&lt;");
            else if (c == '>')
                sb.append("&gt;");
            else if (c == '\n')
                // Handle Newline
                sb.append("&lt;br/&gt;");
            else {
                int ci = 0xffff & c;
                if (ci < 160 )
                    // nothing special only 7 Bit
                    sb.append(c);
                else {
                    // Not 7 Bit use the unicode system
                    sb.append("&#");
                    sb.append(new Integer(ci).toString());
                    sb.append(';');
                    }
                }
            }
        }
    return sb.toString();
}


  /****************************************************************************/
  /** Checks if a given string represents true or false.  Anything beginning
   *  "Y" or "T" is regarded as true; anything else false.
   *
   * @param s String to check
   * @return True or false
   */

  public static boolean trueFalse (String s)
  {
    s = s.toLowerCase();
    return s.startsWith("s") || s.startsWith("t");
  }






  /****************************************************************************/
  /****************************************************************************/
  /**                                                                        **/
  /**                               Private                                  **/
  /**                                                                        **/
  /****************************************************************************/
  /****************************************************************************/

  /****************************************************************************/
  /* Support for the public version of deleteFoldersFromFolder, qv. */

  private static void deleteFoldersFromFolder (File f, boolean deleteMe)
  {
    if (f.isDirectory())
    {
      File[] children = f.listFiles();
      for (int i = 0; i < children.length; ++i) deleteFoldersFromFolder(children[i], true);
    }

    if (deleteMe) f.delete();
  }


  /****************************************************************************/
  private static String m_Suffix; // Used by deleteFilesFromFolder, qv.
}
